using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using System.Windows.Forms;

using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.Server;
using Microsoft.TeamFoundation.Proxy;
using Microsoft.TeamFoundation.VersionControl.Client;
//using Microsoft.TeamFoundation.WorkItemTracking.Controls;
using Microsoft.TeamFoundation.WorkItemTracking.Client;
using System.Drawing;
using System.Xml;
using System.Net;

namespace Lexware.InhouseTools.TeamFoundation
{
    #region TFS Version Control Client class
    public class TfsVersionControlClient
    {
        #region Exception classes
        class FileAlreadyCheckedOutException : InvalidOperationException
        {
            public FileAlreadyCheckedOutException()
                : base()
            {
            }
            public FileAlreadyCheckedOutException(string message)
                : base(message)
            {
            }
        }
        class NothingToDoException : InvalidOperationException
        {
            public NothingToDoException()
                : base()
            {
            }
            public NothingToDoException(string message)
                : base(message)
            {
            }
        }
        #endregion

        #region Private members
        private TeamFoundationServer _tfs;
        private VersionControlServer _versionControl;
        private WorkItemStore _workItemStore;
        private string _tfsUrl;
        private string _userName;
        private string _domain;
        private string _password;
        private bool _useWindowsAuthentication;
        #endregion

        #region Constructor
        public TfsVersionControlClient(string tfsUrl)
        {
            _tfsUrl = tfsUrl;
            _useWindowsAuthentication = true;
        }

        public TfsVersionControlClient(string tfsUrl, string userName, string domain, string password)
        {
            _tfsUrl = tfsUrl;
            _useWindowsAuthentication = false;
            _userName = userName;
            _domain = domain;
            _password = password;
        }
        #endregion

        #region Public methods
        public void Connect()
        {
            if (_useWindowsAuthentication)
            {
                _tfs = new TeamFoundationServer(_tfsUrl);
            }
            else
            {
                ICredentials credentials;
                if (string.IsNullOrEmpty(_domain))
                    credentials = new NetworkCredential(_userName, _password);
                else
                    credentials = new NetworkCredential(_userName, _password, _domain);
                _tfs = new TeamFoundationServer(_tfsUrl, credentials);
            }
            _versionControl = (VersionControlServer)_tfs.GetService(typeof(VersionControlServer));

            _versionControl.NonFatalError += OnNonFatalError;
            _versionControl.Getting += OnGetting;
            _versionControl.BeforeCheckinPendingChange += OnBeforeCheckinPendingChange;
            _versionControl.NewPendingChange += OnNewPendingChange;

            _workItemStore = (WorkItemStore)_tfs.GetService(typeof(WorkItemStore));
        }
        public void Checkout(string fileName)
        {
            Workspace workspace = _versionControl.GetWorkspace(fileName);

            PendingChange[] pendingChanges = workspace.GetPendingChanges(fileName);
            if (pendingChanges.Length > 0)
                throw new FileAlreadyCheckedOutException(fileName + " already has pending changes. Check-in or undo first!");

            workspace.PendEdit(fileName);
        }
        public void Compare(string fileName)
        {
            Workspace workspace = _versionControl.GetWorkspace(fileName);

            PendingChange[] pendingChanges = workspace.GetPendingChanges(fileName);
            if (pendingChanges.Length == 0)
                throw new NothingToDoException(fileName + " is not checked out.");

            if ((pendingChanges[0].ChangeType != Microsoft.TeamFoundation.VersionControl.Client.ChangeType.Edit)
                || (pendingChanges.Length > 1))
                throw new NothingToDoException(fileName + " has more than one pending change, or it is not an 'Edit'.");

            PendingChange edit = pendingChanges[0];
            edit.DownloadBaseFile(fileName + ".original");

            string bc2Path = Environment.GetEnvironmentVariable("BC2");
            string bc2Args = string.Format("\"{0}\" \"{1}\"", fileName + ".original", fileName);
            Process process = Process.Start(bc2Path, bc2Args);
            process.WaitForExit();
        }

        public void Undo(string fileName)
        {
            Console.WriteLine("Undoing changes on " + fileName);
            Workspace workspace = _versionControl.GetWorkspace(fileName);
            workspace.Undo(fileName);
        }

        public void GetLatest(string workspacePath)
        {
            Workspace workspace = _versionControl.GetWorkspace(workspacePath);
            PendingChange[] pendingChanges = workspace.GetPendingChanges(workspacePath, RecursionType.Full, false);
            if (pendingChanges.Length != 0)
                throw new TargetHasPendingChangeException("Cannot get latest with Pending changes in workspace.");
            GetStatus getStatus = workspace.Get(new string[] { workspacePath }, VersionSpec.Latest, RecursionType.Full, GetOptions.None);
            Failure[] failures = getStatus.GetFailures();
            if (null != failures && failures.Length > 0)
            {
                string message = "Get latest rendered the following failures:\n";
                foreach (Failure failure in failures)
                {
                    message += failure.ToString();
                }
                throw new InvalidOperationException(message);
            }
        }

        public string[] EnumerateFiles(string workspacePath)
        {
            Workspace workspace = _versionControl.GetWorkspace(workspacePath);
            string serverPath = workspace.GetServerItemForLocalItem(workspacePath);

            List<string> sourceCodeFiles = new List<string>();
            ItemSet itemSet = _versionControl.GetItems(serverPath, RecursionType.Full);

            foreach (Item item in itemSet.Items)
            {
                string localItem = workspace.TryGetLocalItemForServerItem(item.ServerItem);
                if (null != localItem)
                {
                    sourceCodeFiles.Add(localItem);
                }
            }

            return sourceCodeFiles.ToArray();
        }

        public string[] EnumerateTeamProjects()
        {
            List<string> projectsList = new List<string>();
            foreach (Project project in _workItemStore.Projects)
            {
                projectsList.Add(project.Name);
            }
            string[] projects = new string[projectsList.Count];
            projectsList.CopyTo(projects);

            return projects;
        }

        public string[] EnumerateIterations(string project)
        {
            ICommonStructureService css = (ICommonStructureService)_tfs.GetService(typeof(ICommonStructureService));
            ProjectInfo info = css.GetProjectFromName(project);
            NodeInfo[] nodes = css.ListStructures(info.Uri);

            int iterationNode = -1;
            for (int i = 0; i < nodes.Length; ++i)
            {
                if (nodes[i].Name == "Iteration")
                {
                    iterationNode = i;
                    break;
                }
            }

            if (iterationNode == -1)
            {
                return new string[] { };
            }

            XmlElement iterationXml = css.GetNodesXml(new string[] { nodes[iterationNode].Uri }, true);
            XmlNode iterNode = iterationXml.ChildNodes[0];
            List<string> paths = new List<string>();
            if (iterNode.FirstChild != null)
            {
                BuildIterationPathList(iterNode.FirstChild, project, ref paths);
            }

            return paths.ToArray();
        }

        public string[] EnumerateWorkItemTypes(string project)
        {
            List<string> workItemTypes = new List<string>();
            foreach (WorkItemType type in _workItemStore.Projects[project].WorkItemTypes)
            {
                workItemTypes.Add(type.Name);
            }
            return workItemTypes.ToArray();
        }

        public string[] EnumerateFieldNames(string project, string workItemType)
        {
            List<string> fieldNames = new List<string>();
            WorkItemType type = _workItemStore.Projects[project].WorkItemTypes[workItemType];
            foreach (FieldDefinition field in type.FieldDefinitions)
            {
                fieldNames.Add(field.ReferenceName);
            }
            return fieldNames.ToArray();
        }

        private void BuildIterationPathList(XmlNode node, string project, ref List<string> paths)
        {
            int count = node.ChildNodes.Count;
            for (int i = 0; i < count; ++i)
            {
                XmlNode child = node.ChildNodes[i];
                string rawPath = child.Attributes["Path"].Value.ToString();
                string path = rawPath.Replace(string.Format("\\{0}\\Iteration\\", project), string.Format("{0}\\", project));
                paths.Add(path);
                if (child.ChildNodes.Count > 0)
                {
                    BuildIterationPathList(child.FirstChild, project, ref paths);
                }
            }
        }

        public WorkItem CreateNewWorkItem(string project, string type)
        {
            WorkItem workItem = _workItemStore.Projects[project].WorkItemTypes[type].NewWorkItem();
            return workItem;
        }

        public WorkItem CreateNewTask(string project)
        {
            WorkItem workItem = CreateNewWorkItem(project, "Task");
            return workItem;
        }

        public WorkItem CreateNewBug(string project)
        {
            WorkItem workItem = CreateNewWorkItem(project, "Bug");
            return workItem;
        }

        public void SetWorkItemParentLink(WorkItem workItem, int parentId)
        {
            WorkItemLinkType type = null;
            if (!_workItemStore.WorkItemLinkTypes.TryGetByName("System.LinkTypes.Hierarchy", out type))
            {
                throw new InvalidOperationException("WorkItemLinkType System.LinkTypes.Hierarchy could not be retrieved!");
            }
            WorkItemLinkTypeEnd linkTypeEnd = _workItemStore.WorkItemLinkTypes.LinkTypeEnds[type.ReverseEnd.Name];
            workItem.Links.Add(new RelatedLink(linkTypeEnd, parentId));
        }

        public int DisplayNewTaskDialog(string project)
        {
            WorkItem workItem = CreateNewTask(project);
            return DisplayWorkItemDialog(workItem);
        }

        public int DisplayNewBugDialog(string project)
        {
            WorkItem workItem = CreateNewBug(project);
            return DisplayWorkItemDialog(workItem);
        }

        public WorkItem GetWorkItemById(int workItemId)
        {
            WorkItem workItem = _workItemStore.GetWorkItem(workItemId);
            return workItem;
        }

        public int DisplayWorkItemDialog(int workItemId)
        {
            WorkItem workItem = GetWorkItemById(workItemId);
            if (null == workItem)
            {
                throw new ArgumentException("Work item ID is not valid (or you do not have sufficient rights): #" + workItemId);
            }

            return DisplayWorkItemDialog(workItem);
        }

        public int DisplayWorkItemDialog(WorkItem workItem)
        {
            WorkItemForm form = new WorkItemForm(workItem);
            DialogResult result = form.ShowDialog();

            if (DialogResult.OK == result)
            {
                workItem.Save();

                int workItemId = workItem.Id;

                return workItemId;
            }

            return 0;
        }

        public WorkItemCollection GetWorkItemsFromQuery(string query)
        {
            WorkItemCollection wiCollection = _workItemStore.Query(query);
            return wiCollection;
        }

        public class StateTransitions
        {
            public string State { get; set; }
            public string[] NextStates { get; set; }
        }

        private Dictionary<string, Dictionary<string, StateTransitions[]>> _nextStateCache = new Dictionary<string, Dictionary<string, StateTransitions[]>>();
        public string[] GetPossibleNextStates(string project, string workItemType, string state)
        {
            Dictionary<string, StateTransitions[]> projectCache;
            if (_nextStateCache.ContainsKey(project))
            {
                projectCache = _nextStateCache[project];
            }
            else
            {
                projectCache = new Dictionary<string, StateTransitions[]>();
                _nextStateCache[project] = projectCache;
            }
            if (!projectCache.ContainsKey(workItemType))
            {
                projectCache[workItemType] = BuildStateTransitionList(project, workItemType);
            }

            foreach (StateTransitions transitions in projectCache[workItemType])
            {
                if (transitions.State.Equals(state))
                {
                    return transitions.NextStates;
                }
            }
            return new string[] { };
        }

        private StateTransitions[] BuildStateTransitionList(string project, string workItemType)
        {
            XmlDocument witXml = _workItemStore.Projects[project].WorkItemTypes[workItemType].Export(false);
            XmlNodeList stateNodes = witXml.SelectNodes("//WORKITEMTYPE/WORKFLOW/STATES/STATE");
            Dictionary<string, List<string>> stateTransitions = new Dictionary<string, List<string>>();
            foreach (XmlNode stateNode in stateNodes)
            {
                string stateName = stateNode.Attributes["value"].Value;
                stateTransitions[stateName] = new List<string>();
            }
            XmlNodeList transitionNodes = witXml.SelectNodes("//WORKITEMTYPE/WORKFLOW/TRANSITIONS/TRANSITION");
            foreach (XmlNode transition in transitionNodes)
            {
                string fromName = transition.Attributes["from"].Value;
                string toName = transition.Attributes["to"].Value;
                if (string.IsNullOrEmpty(fromName))
                    continue;
                if (!stateTransitions.ContainsKey(fromName))
                    continue;
                if (stateTransitions[fromName].Contains(toName))
                    continue;
                stateTransitions[fromName].Add(toName);
            }

            List<StateTransitions> tempList = new List<StateTransitions>();
            foreach (string key in stateTransitions.Keys)
            {
                StateTransitions st = new StateTransitions();
                st.State = key;
                List<string> targets = stateTransitions[key];
                if (!targets.Contains(key))
                    targets.Insert(0, key);
                st.NextStates = targets.ToArray();
                tempList.Add(st);
            }

            return tempList.ToArray();
        }

        #endregion

        #region Event handler methods
        private void OnNonFatalError(Object sender, ExceptionEventArgs e)
        {
            if (e.Exception != null)
            {
                Console.Error.WriteLine("Non-fatal exception: " + e.Exception.Message);
            }
            else
            {
                Console.Error.WriteLine("Non-fatal failure: " + e.Failure.Message);
            }
        }
        private void OnGetting(Object sender, GettingEventArgs e)
        {
            Console.WriteLine("Getting: " + e.TargetLocalItem + ", status: " + e.Status);
        }
        private void OnBeforeCheckinPendingChange(Object sender, ProcessingChangeEventArgs e)
        {
            Console.WriteLine("Checking in " + e.PendingChange.LocalItem);
        }
        private void OnNewPendingChange(Object sender, PendingChangeEventArgs e)
        {
            Console.WriteLine("Pending " + PendingChange.GetLocalizedStringForChangeType(e.PendingChange.ChangeType) +
                              " on " + e.PendingChange.LocalItem);
        }
        #endregion
    }
    #endregion

    //public class AreaIterationNode
    //{
    //    public AreaIterationNode(string path, int id)
    //    {
    //        _path = path;
    //        _id = id;
    //    }

    //    private string _path;
    //    public string Path { get { return _path; } }

    //    private int _id;
    //    public int Id { get { return _id; } }

    //    public override string ToString()
    //    {
    //        if (string.IsNullOrEmpty(_path))
    //            return "";
    //        return _path;
    //    }
    //}
}
